#include "vmLibrary.h"
#include "Exception.h"

#include <boost/lexical_cast.hpp>

#include <string>
#include <cassert>
#include <fstream>
#include <map>
#include <functional>
#include <algorithm>
#include <sstream>
#include <regex>

#define BLANK " "

class vmLibrary::Private {
public:
	static std::unique_ptr< vmLibrary, std::function< void ( vmLibrary * ) > > self;
	static void destroy( vmLibrary * p );

	Private();

	void loadUBVMDesFile( const std::string & path, const std::string & logPath );
	void loadCompositeDesFile( const std::string & path, const std::string & logPath );
	void loadDesSubFile( const std::string & path, bool isUBVM, std::ostream & log );
//	void copyToLibrary( int startindex, int endindex, const std::string & defaultstring );

	std::vector< MNParameter > parseParameters( std::istream & in, std::ostream & log );
	void parseVMDescriptors( std::istream & in, bool isUBVM, std::ostream & log );
	std::string parseDescription( std::istream & in, std::ostream & log );
	std::string parseCompositeBody( std::istream & in, std::ostream & log );

	std::map< std::string, int > vm_ids;
	std::map< int, std::string > id_vms;
//	std::vector< vm_des_type > vmCollection;
//	int          vmTotal;					// how many VMs are stored in this VMLibrary
//	int          vmDefaultInportNumber;	// UBVM have default inport number, it is described in VM.des
	std::vector< vmdescriptor >    	dpts;		// the array to store the vm information
	std::vector< MNParameter >      defaultInputParameters;				// default string obtained from VM.des
	std::vector< MNParameter >      defaultOutputParameters;				// default string obtained from VM.des
};

namespace {

	void mergeParameters( std::vector< MNParameter > & parameters, const std::vector< MNParameter > & newParameters ) {
		for( auto oit = newParameters.begin(); oit != newParameters.end(); ++oit ) {
			auto tmp = std::find_if( parameters.begin(), parameters.end(), [oit]( const MNParameter & p )->bool {
				return p.name == oit->name;
			} );
			if( tmp == parameters.end() ) {
				parameters.push_back( *oit );
			}
		}
	}

}

std::string CLASS_to_string( MNClass classtype ) {
	switch( classtype ) {
	case CLASS_REQUIRED:
		return "required";
	case CLASS_DEFAULT:
		return "default";
	case CLASS_HIDDEN:
		return "hidden";
	default:
		throw Exception( "unknown mapping node type", __FILE__, __LINE__ );
	}
}

MNClass string_to_CLASS( const std::string & classtype ) {
	if( classtype == "required" ) {
		return CLASS_REQUIRED ;
	} else if( classtype == "default" ) {
		return CLASS_DEFAULT ;
	} else if( classtype == "hidden" ) {
		return CLASS_HIDDEN ;
	}
	throw Exception( "unknown mapping node type", __FILE__, __LINE__ );
}

std::string CATEGORY_to_string( MNCategory category ) {
	switch(category){
	case CATEGORY_NAME:
		return "NAME";
	case CATEGORY_POSITION:
		return "POSITION";
	case CATEGORY_COLOR:
		return "COLOR";
	case CATEGORY_SCALE:
		return "SCALE";
	case CATEGORY_ROTATION:
		return "ROTATION";
	case CATEGORY_SIZE:
		return "SIZE";
	case CATEGORY_OTHER:
		return "OTHER";
	default:
		throw Exception( "unknown mapping node category", __FILE__, __LINE__ );
	}
}

MNCategory string_to_CATEGORY( const std::string & category ) {
	if( category == "name" || category == "NAME" ) {
		return CATEGORY_NAME;
	} else if( category == "position" || category == "POSITION" ) {
		return CATEGORY_POSITION;
	} else if( category == "size" || category == "SIZE" ) {
		return CATEGORY_SIZE;
	} else if( category == "color" || category == "COLOR" ) {
		return CATEGORY_COLOR;
	} else if( category == "rotation" || category == "ROTATION" ) {
		return CATEGORY_ROTATION;
	} else if( category == "scale" || category == "SCALE" ) {
		return CATEGORY_SCALE;
	} else if( category == "other" || category == "OTHER" ) {
		return CATEGORY_OTHER;
	} else {
		throw Exception( "unknown mapping node category", __FILE__, __LINE__ );
	}
}


std::string trf( const std::string & name ) {
	if( name == "wop") {
		return "var";
	} else if( name == "wops" ) {
		return "vars";
	} else if( name == "var") {
		return "wop";
	} else if( name == "vars") {
		return "wops";
	} else {
		return name;
	}
}

const std::string UBVMDES_FILE_PATH = "DES\\UBVMs.des";
const std::string COMPOSITEDES_FILE_PATH = "DES\\CompositeVMs.des";

std::unique_ptr< vmLibrary, std::function< void ( vmLibrary * ) > > vmLibrary::Private::self( nullptr, Private::destroy );

void vmLibrary::Private::destroy( vmLibrary * p ) {
	delete p;
}

vmLibrary::Private::Private():
vm_ids(),
id_vms(),
//vmCollection(),
//vmDefaultInportNumber( -1 ),
dpts(),
defaultInputParameters(),
defaultOutputParameters() {
}

std::vector< MNParameter > vmLibrary::Private::parseParameters( std::istream & in, std::ostream & log ) {
	std::string token;
	in >> token;
	if( token != "{" ) {
		throw Exception( "DES file corrupted", __FILE__, __LINE__ );
	}

	const std::regex portPattern( "\\s*(\\d+)\\s*=\\s*([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s*" );
	int counter = 0;
	std::vector< MNParameter > parameters;
	while( in >> token ) {
		if( token == "}" ) {
			break;
		} else if( token != "port" ) {
			throw Exception( "DES file corrupted", __FILE__, __LINE__ );
		}

		getline( in, token );
		std::smatch result;
		if( !regex_match( token, result, portPattern ) ) {
			throw Exception( "DES file corrupted", __FILE__, __LINE__ );
		}
		if( boost::lexical_cast< int >( result[1] ) != counter ) {
			throw Exception( "DES file corrupted", __FILE__, __LINE__ );
		}
		++counter;

		MNParameter p;
		p.type = result[2];
		p.name = result[3];
		p.klass = string_to_CLASS( result[4] );
		p.defaultValue = result[5];
		p.category = string_to_CATEGORY( result[6] );
		parameters.push_back( p );
	}
	if( token != "}" ) {
		// if this DES file ends w/out '}'
		throw Exception( "DES file corrupted", __FILE__, __LINE__ );
	}

	return parameters;
}

void vmLibrary::Private::parseVMDescriptors( std::istream & in, bool isUBVM, std::ostream & log ) {
	std::string token;
	in >> token;
	if( token != "{" ) {
		throw Exception( "DES file corrupted", __FILE__, __LINE__ );
	}

	const std::regex vmPattern( "\\s*=\\s*([^\\s]+)\\s+path\\s*=\\s*([^\\s]+)\\s*" );
	int counter = 0;
	while( in >> token ) {
		if( token == "}" ) {
			break;
		} else if( token != "idname" ) {
			throw Exception( "DES file corrupted", __FILE__, __LINE__ );
		}

		getline( in, token );
		std::smatch result;
		if( !regex_match( token, result, vmPattern ) ) {
			throw Exception( "DES file corrupted", __FILE__, __LINE__ );
		}
		this->vm_ids.insert( make_pair( result[1], counter ) );
		this->id_vms.insert( make_pair( counter, result[1] ) );

		this->loadDesSubFile( result[2], isUBVM, log );
		log << "LOAD " << result[2] << " COMPLETED" << std::endl;

		++counter;
	}
	if( token != "}" ) {
		// if this DES file ends w/out '}'
		throw Exception( "DES file corrupted", __FILE__, __LINE__ );
	}

	// 輸出log檔以便查詢ID對照與在map中的次序
	for( auto mpos = this->vm_ids.begin(); mpos != vm_ids.end(); mpos++ ) {
		log << mpos->first << " " << mpos->second << std::endl;
	}
	log << "--------" << std::endl;
}

std::string vmLibrary::Private::parseDescription( std::istream & in, std::ostream & log ) {
	std::string token;
	in >> token;
	if( token != "{" ) {
		throw Exception( "DES file corrupted", __FILE__, __LINE__ );
	}

	std::ostringstream sout;
	in >> token;
	while( token != "}" ) {
		sout << token << " ";
		in >> token;
	}

	return sout.str();
}

std::string vmLibrary::Private::parseCompositeBody( std::istream & in, std::ostream & log ) {
	std::string token;
	in >> token;
	if( token != "{" ) {
		throw Exception( "DES file corrupted", __FILE__, __LINE__ );
	}

	std::ostringstream sout;
	in >> token;
	while( token != "}" ) {
		sout << token << " ";
		in >> token;
	}

	return sout.str();
}

///讀取des檔
void vmLibrary::Private::loadUBVMDesFile( const std::string & path, const std::string & logPath ) {
	std::ifstream desfile( path.c_str(), std::ios_base::in );
	std::ofstream output( logPath.c_str(), std::ios_base::out );
	if( !desfile.is_open() ) {
		throw Exception( "file not open", __FILE__, __LINE__ );
	}
	
	std::string token;
	const std::regex headerPattern( "\\s*#DES\\s+version\\s+2\\.2\\s*" );
	std::smatch result;

	// check header
	getline( desfile, token );
	if( !regex_match( token, result, headerPattern ) ) {
		throw Exception( "DES file version is not 2.2", __FILE__, __LINE__ );
	}

	// read section
	while( desfile >> token ) {
		if( token != "SECTION" ) {
			throw Exception( "DES file corrupted", __FILE__, __LINE__ );
		}

		desfile >> token;
		if( token == "default_parameters" ) {
			// parse default input parameters
			this->defaultInputParameters = this->parseParameters( desfile, output );
		} else if( token == "output_parameters" ) {
			// parse default output parameters
			this->defaultOutputParameters = this->parseParameters( desfile, output );
		} else if( token == "vm_descriptors" ) {
			// parse VM descriptors
			this->parseVMDescriptors( desfile, true, output );
		} else {
			throw Exception( "invalid section in DES file", __FILE__, __LINE__ );
		}
	}

	desfile.close();
}

void vmLibrary::Private::loadCompositeDesFile( const std::string & path, const std::string & logPath ) {
	std::ifstream desfile( path.c_str(), std::ios_base::in );
	std::ofstream output( logPath.c_str(), std::ios_base::out );
	if( !desfile.is_open() ) {
		throw Exception( "file not open", __FILE__, __LINE__ );
	}
	
	std::string token;
	const std::regex headerPattern( "\\s*#DES\\s+version\\s+2\\.2\\s*" );
	std::smatch result;

	// check header
	getline( desfile, token );
	if( !regex_match( token, result, headerPattern ) ) {
		throw Exception( "DES file version is not 2.2", __FILE__, __LINE__ );
	}

	// read section
	while( desfile >> token ) {
		if( token != "SECTION" ) {
			throw Exception( "DES file corrupted", __FILE__, __LINE__ );
		}

		desfile >> token;
		if( token == "vm_descriptors" ) {
			try {
				// parse VM descriptors
				this->parseVMDescriptors( desfile, false, output );
			} catch( Exception & e ) {
				// ignore error DES
				output << path << ": " << e.what() << std::endl;
			}
		} else {
			throw Exception( "invalid section in DES file", __FILE__, __LINE__ );
		}
	}

	//輸出log檔以便查詢ID對照與在map中的次序
	for( auto mpos = vm_ids.begin(); mpos!=vm_ids.end(); mpos++ ) {
		output << mpos->first << " " << mpos->second << std::endl;
	}
	output << "--------" << std::endl;

	desfile.close();
	
}

///讀取des主檔中的各路徑子檔
void vmLibrary::Private::loadDesSubFile( const std::string & path, bool isUBVM, std::ostream & log ) {
	std::ifstream subfile( path.c_str(), std::ios_base::in );
	if( !subfile.is_open() ) {
		throw Exception( "file not open", __FILE__, __LINE__ );
	}
	
	std::string token;
	const std::regex headerPattern( "\\s*#DES\\s+version\\s+2\\.2\\s*" );
	std::smatch result;

	// check header
	getline( subfile, token );
	if( !regex_match( token, result, headerPattern ) ) {
		throw Exception( "DES file version is not 2.2", __FILE__, __LINE__ );
	}

	vmdescriptor des_type;
	des_type.inputParameters = this->defaultInputParameters;
	des_type.outputParameters = this->defaultOutputParameters;
	const std::regex propPattern( "\\s*=\\s*([^\\s]+)\\s*" );
	// read section
	while( subfile >> token ) {
		if( token == "IDNAME" ) {
			getline( subfile, token );
			if( !regex_match( token, result, propPattern ) ) {
				throw Exception( "DES file corrupted", __FILE__, __LINE__ );
			}
			des_type.vm_name = result[1];
			des_type.idname = result[1];
			des_type.id = this->vm_ids.find( result[1] )->second;
		} else if( token == "CATEGORY" ) {
			getline( subfile, token );
			if( !regex_match( token, result, propPattern ) ) {
				throw Exception( "DES file corrupted", __FILE__, __LINE__ );
			}
			des_type.vm_category = result[1];
		} else if( token == "TYPE" ) {
			getline( subfile, token );
			if( !regex_match( token, result, propPattern ) ) {
				throw Exception( "DES file corrupted", __FILE__, __LINE__ );
			}
			des_type.vm_type = result[1];
		} else if( token == "SECTION" ) {
			subfile >> token;
			if( token == "description" ) {
				// parse description
				des_type.vm_description = this->parseDescription( subfile, log );
			} else if( token == "parameters" ) {
				// parse default input parameters
				auto tmp = this->parseParameters( subfile, log );
				// FIXME dirty hack, default parameters should not appears in here
				mergeParameters( des_type.inputParameters, tmp );
			} else if( token == "output_parameters" ) {
				// parse VM descriptors
				auto tmp = this->parseParameters( subfile, log );
				// FIXME dirty hack, default parameters should not appears in here
				mergeParameters( des_type.outputParameters, tmp );
			} else if( token == "compositebody" ) {
				des_type.compositebody_mds = this->parseCompositeBody( subfile, log );
			} else {
				throw Exception( "invalid section in DES file", __FILE__, __LINE__ );
			}
		} else if( token == "END" ) {
			break;
		} else {
			throw Exception( "DES file corrupted", __FILE__, __LINE__ );
		}
	}

	this->dpts.push_back( des_type );
}

/// constructor
vmLibrary::vmLibrary():
p_( new Private ) {
//	vmTotal = 0 ;

	// first, we load all hte UBVMs. All the UBVMs share the same default string described in
	// UBVMs.des
	this->p_->loadUBVMDesFile( UBVMDES_FILE_PATH, "DES/des_log.txt" );

	// Second, we load the customized, composite VMs. These VMs do not have default string
	this->p_->loadCompositeDesFile( COMPOSITEDES_FILE_PATH, "DES/compositedes_log.txt" );
}
/// destructor
vmLibrary::~vmLibrary(){
}

/// singleton method 
vmLibrary & vmLibrary::instance() {
	if( !Private::self ) {
		Private::self.reset( new vmLibrary );
	}
	return *Private::self;
}

vmdescriptor * vmLibrary::get_vmdescriptor( int i ) const {
	assert( this->p_->dpts[i].id == i || "descriptor id mismatch" );
	return &(this->p_->dpts[i]) ;
}

vmdescriptor * vmLibrary::get_vmdescriptor( const std::string & idname ) const {
	auto it = std::find_if( this->p_->dpts.begin(), this->p_->dpts.end(), [&idname]( const vmdescriptor & d )->bool {
		return d.idname == idname;
	} );
	if( it == this->p_->dpts.end() ) {
		throw Exception( "this id does not exists", __FILE__, __LINE__ );
	}
	return &*it;
}

int vmLibrary::get_vm_total() const {
	return this->p_->dpts.size();
}

const std::vector< MNParameter > & vmLibrary::get_vm_default_input_parameters() const {
	return this->p_->defaultInputParameters;
}

const std::vector< MNParameter > & vmLibrary::get_vm_default_output_parameters() const {
	return this->p_->defaultOutputParameters;
}

int vmLibrary::get_id_by_name( const std::string & idname ) const {
	auto it = this->p_->vm_ids.find( idname );
	if( it == this->p_->vm_ids.end() ) {
		throw Exception( "id not found", __FILE__, __LINE__ );
	}
	return it->second;
}

std::string vmLibrary::get_name_by_id( int id ) const {
	auto it = this->p_->id_vms.find( id );
	if( it == this->p_->id_vms.end() ) {
		throw Exception( "id not found", __FILE__, __LINE__ );
	}
	return it->second;
}
